#include "client.h"

// 单例模式，实例化函数
client *client::newInstance()
{
    if (client::instance == nullptr)
    {
        client::instance = new client;
    }
    return client::instance;
}

string client::currentPushUrl()
{
    string p_host = PUSHGATEWAY_G(host);
    string document_root = "", job_name = "";

    // 获取job_name
    string sapi = sapi_module.name;
    if (sapi == "fpm-fcgi")
    {
        document_root = server_param("DOCUMENT_ROOT");
        document_root = document_root == "" ? "unknown" : document_root;
    }
    else if (sapi == "cli")
    {
        document_root = server_param("SCRIPT_FILENAME");
    }
    document_root = document_root.find_first_of("/") == 0 ? document_root.substr(1) : document_root;
    job_name = str_replace_all(document_root, "/", "-");
    // 获取node_name
    char *node_name = new char[256];
    gethostname(node_name, 256);
    // 获取instance
    int pid = (int)getpid();
    string instance = to_string(pid);
    // 上报地址
    string url = p_host + "/metrics/job/" + job_name + "/nodename/" + node_name + "/instance/" + instance;

    return url;
}

// 设置上报数据
void client::setData(string type, string key, string index, string item, long step, int decimal)
{
    string url = this->currentPushUrl();
    if (this->data.count(url) <= 0 || this->data[url].count(type) <= 0 || this->data[url][type].count(key) <= 0 || this->data[url][type][key].count(index) <= 0 || this->data[url][type][key][index].count(item) <= 0)
    {
        push_value pv;
        pv.decimal = decimal;
        pv.value = step;

        this->data[url][type][key][index][item] = pv;
    }
    else
    {
        long total = this->data[url][type][key][index][item].value + step;
        this->data[url][type][key][index][item].value = total;
        this->data[url][type][key][index][item].decimal = decimal;
    }
}

// 设置上报数据
void client::setData(string type, string key, string index, string item, long step)
{
    this->setData(type, key, index, item, step, 0);
}

// 设置上报数据
void client::setData(string type, string key, string index, string item)
{
    this->setData(type, key, index, item, 1, 0);
}

// 执行上报线程
void client::run(int internal)
{
    if (this->runnable)
    {
        int p_timeout = PUSHGATEWAY_G(timeout);
        int p_connecttimeout = PUSHGATEWAY_G(connecttimeout);

        thread th(send, internal, p_timeout, p_connecttimeout);
        th.detach();
    }

    this->runnable = false;
}

// 执行上报操作
void client::send(int interval, int p_timeout, int p_connecttimeout)
{
    // 只要进程不被销毁，就会定时上报
    while (true)
    {
        if (!client::instance->data.empty())
        {
            map<string, map<string, map<string, map<string, map<string, push_value>>>>>::iterator url_iter;
            for (url_iter = client::instance->data.begin(); url_iter != client::instance->data.end(); url_iter++)
            {
                // 获取上报地址
                string url = url_iter->first;
                char *url_c = new char[url.length() + 1];
                strcpy(url_c, url.c_str());

                // 获取上报报文
                string request = client::instance->toString(url);
                char *request_c = new char[request.length() + 1];
                strcpy(request_c, request.c_str());

                if (request != "")
                {
                    CURL *curl = curl_easy_init();

                    struct curl_slist *header_list = nullptr;
                    header_list = curl_slist_append(header_list, "Content-Type: text/plain; charset=utf-8");

                    curl_easy_setopt(curl, CURLOPT_URL, url_c);
                    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
                    curl_easy_setopt(curl, CURLOPT_POST, 1);
                    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_c);
                    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, p_connecttimeout);
                    curl_easy_setopt(curl, CURLOPT_TIMEOUT, p_timeout);

                    curl_easy_perform(curl);
                    curl_easy_cleanup(curl);

                    // 调试模式进行文件写操作
                    if (client::instance->debug)
                    {
                        ofstream ofs;
                        // 写文件
                        ofs.open(client::instance->output, ios::app);
                        ofs << url << endl
                            << request << endl;
                        ofs.close();
                    }

                    client::instance->clean();
                }
            }
        }

        this_thread::sleep_for(chrono::seconds(interval));
    }
}

void client::syncSend()
{
    if (!client::instance->data.empty())
    {
        int p_timeout = PUSHGATEWAY_G(timeout);
        int p_connecttimeout = PUSHGATEWAY_G(connecttimeout);
        map<string, map<string, map<string, map<string, map<string, push_value>>>>>::iterator url_iter;
        for (url_iter = client::instance->data.begin(); url_iter != client::instance->data.end(); url_iter++)
        {
            // 获取上报地址
            string url = url_iter->first;
            char *url_c = new char[url.length() + 1];
            strcpy(url_c, url.c_str());

            // 获取上报报文
            string request = client::instance->toString(url);
            char *request_c = new char[request.length() + 1];
            strcpy(request_c, request.c_str());

            if (request != "")
            {
                CURL *curl = curl_easy_init();

                struct curl_slist *header_list = nullptr;
                header_list = curl_slist_append(header_list, "Content-Type: text/plain; charset=utf-8");

                curl_easy_setopt(curl, CURLOPT_URL, url_c);
                curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
                curl_easy_setopt(curl, CURLOPT_POST, 1);
                curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_c);
                curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, p_connecttimeout);
                curl_easy_setopt(curl, CURLOPT_TIMEOUT, p_timeout);

                curl_easy_perform(curl);
                curl_easy_cleanup(curl);

                // 调试模式进行文件写操作
                if (client::instance->debug)
                {
                    ofstream ofs;
                    // 写文件
                    ofs.open(client::instance->output, ios::app);
                    ofs << url << endl
                        << request << endl;
                    ofs.close();
                }

                client::instance->clean();
            }
        }
    }
}

void client::setCounter(scaler *sc)
{
    this->setData("counter", sc->getIndex(), sc->getIndex(), sc->getItemsStr(), 1);

    sc = nullptr;
    delete sc;
}

bool compareDoubleString(const string &first, const string &second)
{
    char first_c[first.length() + 1];
    char second_c[second.length() + 1];
    strcpy(first_c, first.c_str());
    strcpy(second_c, second.c_str());
    double first_d = atof(first_c);
    double second_d = atof(second_c);

    return first_d < second_d + EPS;
}

void client::setBucket(scaler *sc, double seconds, string bk)
{
    string index = sc->getIndex();
    map<string, string> items = sc->getItems();
    string sep = ",";
    vector<string> buckets = split(bk, sep);

    // 排序
    sort(buckets.begin(), buckets.end(), compareDoubleString);

    for (auto item : buckets)
    {
        // 创建bucket
        scaler *sc_bucket = new scaler;
        string sc_bucket_index = index + "_bucket";
        sc_bucket->setIndex(sc_bucket_index);
        sc_bucket->setItems(items);
        sc_bucket->addItem(ITEM_LE, item);
        string bucket_key = sc_bucket->getItemsStr();

        // 判断指标落在哪个bucket
        this->setData("histogram", index, sc_bucket_index, bucket_key, 0);
        if (!compareDoubleString(item, doubleToString(seconds, 6)))
        {
            string url = this->currentPushUrl();
            this->data[url]["histogram"][index][sc_bucket_index][bucket_key].value++;
        }

        // 清理内存
        sc_bucket = nullptr;
        delete sc_bucket;
    }

    // 计算bucket时间累积总数
    scaler *sc_bucket_sum = new scaler;
    string sc_bucket_sum_index = index + "_sum";
    sc_bucket_sum->setIndex(sc_bucket_sum_index);
    sc_bucket_sum->setItems(items);
    string bucket_sum_key = sc_bucket_sum->getItemsStr();
    long seconds_i = seconds * pow(10, PUSH_HISTOGRAM_DECIAML);
    this->setData("histogram", index, sc_bucket_sum_index, bucket_sum_key, seconds_i, PUSH_HISTOGRAM_DECIAML);

    sc_bucket_sum = nullptr;
    delete sc_bucket_sum;

    // 计算bucket请求累积总数
    scaler *sc_bucket_count = new scaler;
    string sc_bucket_count_index = index + "_count";
    sc_bucket_count->setIndex(sc_bucket_count_index);
    sc_bucket_count->setItems(items);
    string bucket_count_key = sc_bucket_count->getItemsStr();
    this->setData("histogram", index, sc_bucket_count_index, bucket_count_key, 1);

    sc_bucket_count = nullptr;
    delete sc_bucket_count;

    sc = nullptr;
    delete sc;
}

void client::clean()
{
    this->data.erase(this->data.begin(), this->data.end());
}

string client::toString(string url)
{
    string res = "";
    string value = "";

    if (this->data.count(url) <= 0)
    {
        return res;
    }

    map<string, push_value>::iterator item_iter;
    map<string, map<string, push_value>>::iterator index_iter;
    map<string, map<string, map<string, push_value>>>::iterator key_iter;
    map<string, map<string, map<string, map<string, push_value>>>>::iterator type_iter;

    for (type_iter = this->data[url].begin(); type_iter != this->data[url].end(); type_iter++)
    {
        for (key_iter = type_iter->second.begin(); key_iter != type_iter->second.end(); key_iter++)
        {
            res = res + "# TYPE " + key_iter->first + " " + type_iter->first + "\n";
            for (index_iter = key_iter->second.begin(); index_iter != key_iter->second.end(); index_iter++)
            {
                for (item_iter = index_iter->second.begin(); item_iter != index_iter->second.end(); item_iter++)
                {
                    if (item_iter->second.decimal > 0)
                    {
                        double value_f = item_iter->second.value * pow(0.1, PUSH_HISTOGRAM_DECIAML);
                        value = doubleToString(value_f, item_iter->second.decimal);
                    }
                    else
                    {
                        value = to_string(item_iter->second.value);
                    }

                    res = res + index_iter->first + item_iter->first + " " + value + "\n";
                }
            }
        }
    }

    return res;
}

void client::test()
{
    // string res = this->toString();
    // char str[res.length() + 1];
    // strcpy(str, res.c_str());
    // php_printf("path: %s\n", str);

    // httplib::Client cli = new httplib::Client("http://10.16.19.135");
    // httplib::Client cli("http://10.16.19.135");
    // cli.Post("/index.php", this->toString(), "text/plain");
    // string response = res->body;
    // char str[response.length()+1];
    // php_printf("%s\n", str);
}